package main

import (
	"fmt"
	"log"
	"strconv"
	"time"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
)

func initAuthMiddleware() *jwt.GinJWTMiddleware {
	// the jwt middleware
	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "test zone",
		Key:         []byte("secret key"),
		Timeout:     time.Hour * 72,
		MaxRefresh:  time.Hour * 120,
		IdentityKey: identityKey,
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.Username,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				Username: claims[identityKey].(string),
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			// fmt.Println(c)
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			username := loginVals.Username
			password := loginVals.Password

			var user User
			result := db.Where("Username = ?", username).First(&user)
			if result.Error == nil {
				if user.Password == password {
					fmt.Println("登录成功", user)
					return &user, nil
				} else {
					fmt.Println("密码错误", user)
					return nil, jwt.ErrFailedAuthentication
				}
			} else {
				user = User{Username: username, Password: password}
				db.Create(&user)
				fmt.Println("注册账号", user)
				return &user, nil
			}

			// fmt.Println(userID, password)
			// if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
			// 	return &User{
			// 		Username: userID,
			// 		Password: "Bo-Yi",
			// 		IsStaff:  true,
			// 	}, nil
			// }

			return nil, jwt.ErrFailedAuthentication
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			// if v, ok := data.(*User); ok && v.Username == "test" {
			// 	return true
			// }
			return true
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			// c.JSON(code, gin.H{
			// 	"code":    code,
			// 	"message": message,
			// })
			// c.Redirect(302, "/account")
			c.HTML(code, "unauthorized.html", gin.H{})
		},
		// TokenLookup is a string in the form of "<source>:<name>" that is used
		// to extract token from the request.
		// Optional. Default value "header:Authorization".
		// Possible values:
		// - "header:<name>"
		// - "query:<name>"
		// - "cookie:<name>"
		// - "param:<name>"
		TokenLookup: "header: Authorization, query: token, cookie: jwt",
		// TokenLookup: "query:token",
		// TokenLookup: "cookie:token",

		// TokenHeadName is a string in the header. Default value is "Bearer"
		TokenHeadName: "Bearer",

		// TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
		TimeFunc:   time.Now,
		SendCookie: true,
	})

	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}

	// When you use jwt.New(), the function is already automatically called for checking,
	// which means you don't need to call it again.
	errInit := authMiddleware.MiddlewareInit()

	if errInit != nil {
		log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
	}

	return authMiddleware

}

func AccountHandler(c *gin.Context) {
	c.HTML(200, "login.html", gin.H{})
}

func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)
	// fmt.Println("hello", user)

	c.JSON(200, gin.H{
		"userID":   claims[identityKey],
		"userName": user.(*User).Username,
		"text":     "Hello World.",
	})
}

func NewTodoHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)

	c.HTML(200, "newtodo.html", gin.H{
		"userID":   claims[identityKey],
		"userName": user.(*User).Username,
		"text":     "Hello World.",
	})
}

func CreateTodoHandler(c *gin.Context) {
	// claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)

	descStr := c.PostForm("desc")
	isPublic := (c.PostForm("public") == "on")
	// fmt.Println("create todo ====", c.PostForm("public"), pub)
	db.Create(&Todo{UserID: user.(*User).Username, Desc: descStr, Begin: time.Now(), Public: isPublic})

	// c.HTML(200, "todolist.html", gin.H{
	// 	"userID":   claims[identityKey],
	// 	"userName": user.(*User).Username,
	// 	"text":     "创建成功。",
	// })
	c.Redirect(302, "/todo/list")
	// c.Request.URL.Path = ""
	// r.HandleContext(c)
}

func TodoListHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)

	// db.Create(&Todo{Desc: descStr, Begin: time.Now()})
	username := claims[identityKey].(string)
	var user User
	db.Where(User{Username: username}).Find(&user)

	var todos []Todo
	db.Where(Todo{UserID: username}).Find(&todos)
	// fmt.Println("todo list|||", todos)

	c.HTML(200, "todolist.html", gin.H{
		"username": username,
		"is_staff": user.IsStaff,
		"todos":    todos,
	})
}

func FinishTodoHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	username := claims[identityKey].(string)
	tid, _ := strconv.Atoi(c.PostForm("tid"))

	result := db.Model(&Todo{}).Where(Todo{UserID: username, ID: uint(tid)}).Updates(Todo{Finished: true, End: time.Now()})
	if result.RowsAffected > 0 {
		c.String(200, "Success")
	} else {
		c.String(400, "Error")
	}
}

func ApproveListHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	username := claims[identityKey].(string)

	var user User
	db.Where(User{Username: username}).Find(&user)
	// fmt.Println("ApproveListHandler|||", user, user.IsStaff)
	if !user.IsStaff {
		c.Redirect(302, "/todo/list")
		return
	}

	var todos []Todo
	db.Where(map[string]interface{}{"Public": true, "Approved": false}).Find(&todos)
	// fmt.Println("ApproveListHandler|||", todos)
	c.HTML(200, "approvelist.html", gin.H{
		"todos": todos,
	})
}

func ApproveHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	username := claims[identityKey].(string)

	var user User
	db.Where(User{Username: username}).Find(&user)
	// fmt.Println("ApproveListHandler|||", user, user.IsStaff)
	if !user.IsStaff {
		c.Redirect(302, "/todo/list")
		return
	}

	tid, _ := strconv.Atoi(c.PostForm("tid"))

	result := db.Model(&Todo{}).Where(map[string]interface{}{"Public": true, "ID": uint(tid)}).Update("Approved", true)
	if result.RowsAffected > 0 {
		c.String(200, "Success")
	} else {
		c.String(400, "Error")
	}
}

func HomeHandler(c *gin.Context) {
	var todos []Todo
	db.Limit(30).Where(map[string]interface{}{"Public": true, "Approved": true}).Find(&todos)
	c.HTML(200, "home.html", gin.H{
		"todos": todos,
	})
}
